#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "func.h"

char	malloced_var[128][128];
int	malloced_var_num = 0;
int	used_point = 0;

static inline void param_struct_value_serialize(PARAM *pa, char *out)
{
	if(pa->type.is_point)
	{
		sprintf(out, "\t%s_pack(&%s, &p);\n", pa->type.type_name, pa->name);
	}
	else
	{
		sprintf(out, "\t%s_pack(&%s, &p);\n", pa->type.name, pa->name);
	}
}

static void inline param_data_unpack(PARAM *pa, PARAM *pnum, char *out)
{
	sprintf(out, "\tDATA_unpack_nocopy(&p, &%s, &%s);\n", pa->name, pnum->name);
}

static void inline param_struct_point_unpack(PARAM *pa, char *out)
{
	if(pa->type.is_point)
	{
		sprintf(out, "\t%s_unpack(&p, &%s);\n", pa->type.type_name, pa->name);
	}
	else
	{
		sprintf(out, "\t%s_unpack(&p, &%s);\n", pa->type.name, pa->name);
	}
}

static void inline param_array_num_unpack(PARAM *pa, char *out)
{
	sprintf(out, "\n\tARRAY_SIZE_unpack(&p, &%s);\n", pa->name);
}

static void inline gen_array_alloc(PARAM *pa, PARAM *pnum, char *out)
{
	sprintf(out, "\t%s = (%s)malloc(%s * sizeof(%s));\n", pa->name, pa->type.name, pnum->name, pa->type.type_name);
	strcpy(malloced_var[malloced_var_num], pa->name);
	malloced_var_num++;
}


static void inline param_array_unpack(PARAM *pa, PARAM *pnum, char *out)
{
	int  n;

	n = strlen(out);
	sprintf(out + n, "\tfor(i = 0; i < %s; i++)\n", pnum->name);

	strcat(out, "\t{\n");

	n = strlen(out);
	sprintf(out + n, "\t\t%s one = %s + i;\n", pa->type.name, pa->name);

	n = strlen(out);
	if(pa->type.is_point)
	{
		sprintf(out + n, "\t\t%s_unpack(&p, one);\n", pa->type.type_name);
	}
	else
	{
		sprintf(out + n, "\t\t%s_unpack(&p, one);\n", pa->type.name);
	}

	strcat(out, "\t}\n\n");
}

static void inline gen_out_param_array_code(PARAM *pa, PARAM *num, char *out) 
{
	int n = strlen(out);

	if(pa->type.is_point)
	{
	//	if(used_point)
		if(1)
		{
			sprintf(out + n, "\t\t%s_ARRAY_pack(%s, %s, &p);\n", 
					pa->type.type_name, pa->name, num->name);
		}
		else
		{
			sprintf(out + n, "\t\t%s_ARRAY_pack(&%s, %s, &p);\n", 
					pa->type.type_name, pa->name, num->name);
		}
	}
	else
	{
		sprintf(out + n, "\t\t%s_ARRAY_pack(&%s, %s, &p);\n", pa->type.name, pa->name, num->name);
	}
}

void	gen_out_param_code(func_t *func, char *out)
{
	int	n, count = 0;
	PARAM	*one;

	while(1)
	{
		one = func->pa_out + count;
		if(one->desc & PARAM_SINGLE)
		{
			n = strlen(out);
			if(one->type.is_point)
			{
				sprintf(out + n, "\t\t%s_pack(%s, &p);\n", one->type.type_name, one->name);
			}
			else
			{
				sprintf(out + n, "\t\t%s_pack( %s, &p);\n", one->type.name, one->name);
			}
			count++;
		}
		else if(one->desc & PARAM_MULTIP)
		{
			gen_out_param_array_code(one, one + 1, out);
			count += 2;
		}
		else if(one->desc & PARAM_DATA)
		{
			n = strlen(out);
			if(used_point)
			{
				sprintf(out + n, "\t\tDATA_pack_no_copy(%s, %s, &p);\n", one->name, (one + 1)->name);
			}
			else
			{
				sprintf(out + n, "\t\tDATA_pack(%s, %s, &p);\n", one->name, (one + 1)->name);
			}
			count += 2;
		}
		else
		{
			assert(0);
		}
		if(count >= func->out_num)
		{
			break;
		}
	}
}

void	func_out_parse(func_t *func)
{
	int	count = 0;
	PARAM	*one, *cur;

	while(1)
	{
		one = func->pa + count;
		assert(one != NULL);
		if(one->desc & PARAM_OUT)
		{
			if(one->desc & PARAM_SINGLE)
			{
				func->pa_out = (PARAM *)realloc(func->pa_out, sizeof(PARAM) * (func->out_num + 1));
				cur = func->pa_out + func->out_num;
				memset(cur, 0, sizeof(PARAM));
				memcpy(cur, one, sizeof(PARAM));
				func->out_num++;
				count++;
			}
			else if(one->desc & PARAM_MULTIP || one->desc & PARAM_DATA)
			{
				func->pa_out = (PARAM *)realloc(func->pa_out, sizeof(PARAM) * (func->out_num + 2));
				cur = func->pa_out + func->out_num;
				memset(cur, 0, sizeof(PARAM));
				memcpy(cur, one, sizeof(PARAM));

				cur = func->pa_out + (func->out_num + 1);
				memset(cur, 0, sizeof(PARAM));
				memcpy(cur, func->pa + (count + 1), sizeof(PARAM));

				func->out_num += 2;
				count += 2;
			}
			else
			{
				assert(0);
			}
		}
		else
		{
			count++;
		}
		if(count >= func->pa_num)
		{
			break;
		}
	}
}

void	func_parse(func_t *func)
{
	func_out_parse(func);
}

void	gen_var_by_param(func_t *func, char *out)
{
	int	i, n;
	PARAM	*one;

	for(i = 1; i < func->pa_num; i++)
	{
		one = func->pa + i;
		n = strlen(out);

		if((one->type.is_point) && !(one->desc & PARAM_DATA) && !(one->desc & PARAM_MULTIP))
		{
			sprintf(out + n, "\t%s %s;\n", one->type.type_name, one->name);
		}
		else
		{
			sprintf(out + n, "\t%s %s;\n", one->type.name, one->name);
		}
	}
}

void	get_point(func_t *func, char *out) 
{
	int	n;

	assert(func->dataout_num <= 1);
	if(func->dataout_num == 0) {
		return;
	}

	/* in out param: GetAttributeValue */
	if(func->pa_dataout->desc & PARAM_IN){
		return;
	}

	strcat(out, "\tMSG_ENCODE_INIT(out, &p, result);\n");

	n = strlen(out);
	sprintf(out + n, "\t%s = (%s)(p + 2);\n", func->pa_dataout->name, func->pa_dataout->type.name);
	used_point = 1;
}

void	gen_call_function(func_t *func, char *out)
{
	int	i;
	PARAM	*one;
	char	param[512] = {0};

	sprintf(out, "\tresult = %s_inner(ctx", func->name); 

	for(i = 1; i < func->pa_num; i++)
	{
		one = func->pa + i;
		strcat(param, ", ");
		if((one->type.is_point) && !(one->desc & PARAM_DATA) && !(one->desc &PARAM_MULTIP))
		{
			strcat(param, "&");
		}
		strcat(param, one->name);
	}
	strcat(out, param);
	strcat(out, ");\n"); 
}

void	func_gen_backend(func_t *func, char *out)
{
	int	i, n = 0, count;
	PARAM	*pa;

	memset(malloced_var, 0, sizeof(malloced_var));
	malloced_var_num = 0;
	used_point = 0;

	func_parse(func);

	strcat(out, "void\t"); 

	n = strlen(out);
	sprintf(out + n ,"%s_backend", func->name);

	strcat(out, "(");

	strcat(out, "void *ctx, uint8_t *in, uint32_t inlen, uint8_t *out, uint32_t *outlen");

	strcat(out, ")\n");

	strcat(out, "{\n");

	if(func->has_loop)
	{
		strcat(out, "\tint i;\n");
	}

	strcat(out, "\tuint8_t *p;\n");

	strcat(out, "\tuint16_t len;\n");

	n = strlen(out);
	sprintf(out + n, "\tCK_RV result = -1;\n");

	n = strlen(out);
	gen_var_by_param(func, out + n);

	/* unpack start  */
	n = strlen(out);
	sprintf(out + n, "\n\tMSG_DECODE_INIT(in, &p, NULL);\n");

	count = 1;
	while(1)
	{
		pa = func->pa + count;
		if(pa->desc & PARAM_IN)
		{
			n = strlen(out);
			if((pa->desc & PARAM_POINT) && (pa->desc & PARAM_MULTIP))
			{
				param_array_num_unpack(pa + 1, out + n);

				n = strlen(out);
				gen_array_alloc(pa, pa + 1, out + n);

				n = strlen(out);
				param_array_unpack(pa, pa + 1, out + n);
				count += 2;
			}
			else if((pa->desc & PARAM_POINT) && (pa->desc & PARAM_DATA))
			{
				param_data_unpack(pa, pa + 1, out + n);
				count += 2;
			}
			else
			{
				param_struct_point_unpack(pa, out + n);
				count++;
			}
		}
		else
		{
			count++;
		}
		if(count >= func->pa_num)
		{
			break;
		}
	}

#if 1
	n = strlen(out);
	get_point(func, out + n);
#endif

	/* call function */
	n = strlen(out);
	gen_call_function(func, out + n);

	strcat(out, "\tMSG_ENCODE_INIT(out, &p, result);\n");

	/* decode return param */
	if(func->out_num > 0)
	{
		strcat(out, "\tif(result == CKR_OK)\n\t{\n");
		n = strlen(out);
		gen_out_param_code(func, out + n);
		strcat(out, "\t}\n");
	}

	strcat(out, "\n\tlen = p - out - 2;\n");
	strcat(out, "\t*(uint16_t *)out = htons(len);\n");
	strcat(out, "\t(*outlen) = len + 2;\n");

	for(i = 0; i < malloced_var_num; i++)
	{
		n = strlen(out);
		sprintf(out + n, "\tfree(%s);\n", malloced_var[i]);
	}

	strcat(out, "}");
}
